// --------------------------------------------------------------------------------------------------------------------- 
// <copyright file="MovingAverage.cs" company="Scrum for Team System">
//   None
// </copyright>
// <summary>
//   Defines the MovingAverage type.
// </summary>
// ---------------------------------------------------------------------------------------------------------------------
namespace ScrumForTeamSystem.Metrics
{
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Simple Moving Average model. Each Trend line point is the average of the last n Data points (including the current point).
    /// Thus if a 3-day MA is used, and the daily values are
    /// Day 1 = 20
    /// Day 2 = 22
    /// Day 3 = 21
    /// Day 4 = 19
    /// Day 5 = 16
    /// then the Trend line points are:
    /// Day 3 = (20 + 22 + 21) / 3 = 21
    /// Day 4 = (22 + 21 + 19) / 3 = 20.66666
    /// Day 5 = (21 + 19 + 16) / 3 = 18.66666
    /// A n-day MA doesn't start until Day n. The more days, the smoother the line, but the more Data you need before the
    /// Trend can be drawn. All MAs and their variants "lag" behind the real Data line.
    /// </summary>
    internal class MovingAverage : ModelBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MovingAverage"/> class. 
        /// Constructor: sets the period
        /// </summary>
        /// <param name="period">
        /// The number of days in the average.
        /// </param>
        public MovingAverage(int period)
        {
            this.Period = period;
        }

        /// <summary>
        /// Gets or sets the period.
        /// </summary>
        /// <value>The period.</value>
        public int Period
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the trend.
        /// </summary>
        /// <value>The trend.</value>
        protected List<double?> Trend
        {
            get;
            set;
        }

        /// <summary>
        /// Overrides the base class to calculate a Trend line point given a Data point.
        /// </summary>
        /// <returns>a Trend line point, or null</returns>
        public override double? GetNextTrendValue()
        {
            var result = this.PointCount + 1 >= this.Period 
                                 ? this.CalculateTrendPoint() - this.Adjustment 
                                 : null;

            this.PointCount++;
            return result;
        }

        /// <summary>
        /// Overrides the base class to calculate the Trend line
        /// </summary>
        protected override void CalculateTrend()
        {
            var last = this.PointCount - 1;
            this.Trend = new List<double?>(this.Data.Select(d => default(double?)));

            for (var i = 0; i < this.PointCount; i++)
            {
                this.Trend[i] = i + 1 >= this.Period ? this.CalculateAverage(i) : null;
            }

            this.Adjustment = this.IsAdjusted ? (double)this.Trend[last] - (double)this.Data[last] : 0;
        }

        /// <summary>
        /// Calculates the average of the last n Data points. Is overridden in subclasses
        /// to change the method of averaging.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns>The average value.</returns>
        protected virtual double? CalculateAverage(int index)
        {
            double total = 0;
            for (var i = 0; i < this.Period; i++)
            {
                total += (double)this.Data[index - i];
            }

            return total / this.Period;
        }

        /// <summary>
        /// Calculates a Trend line point. If the current Data point has a value (i.e. is not
        /// null), calculates the average of the last n Data points. Otherwise, uses the gradient between the last
        /// two Trend points to calculate this Trend point.
        /// </summary>
        /// <returns>The calculated trend point value.</returns>
        private double? CalculateTrendPoint()
        {
            if (!this.Data[this.PointCount].HasValue)
            {
                this.Trend[this.PointCount] = (2 * this.Trend[this.PointCount - 1]) - this.Trend[this.PointCount - 2];
            }

            return this.Trend[this.PointCount];
        }
    }
}